1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.filesystem.hipfs;
12 
13 public import hip.api.filesystem.hipfs;
14 import hip.util.reflection;
15 
16 /** 
17  * Returns whether if the path attempts to exit the initial one.
18  * Params:
19  *   initial = 
20  *   toAppend = 
21  * Returns: 
22  */
23 private pure bool validatePath(string initial, string toAppend)
24 {
25     import hip.util.array:lastIndexOf;
26     import hip.util.string:splitRange;
27     import hip.util.system : sanitizePath;
28 
29     if(initial.length != 0 && initial[$-1] == '/')
30         initial = initial[0..$-1];
31     scope char[] newPath = initial.sanitizePath;
32     scope char[] appends = toAppend.sanitizePath;
33 
34     scope(exit)
35     {
36         import core.memory:GC;
37         // GC.free(newPath.ptr);
38         // GC.free(appends.ptr);
39     }
40 
41     foreach(a; splitRange(appends, "/"))
42     {
43         if(a == "" || a == ".")
44             continue;
45         if(a == "..")
46         {
47             long lastInd = newPath.lastIndexOf('/');
48             if(lastInd == -1)
49                 continue;
50             newPath = newPath[0..cast(uint)lastInd];
51         }
52         else
53             newPath~= "/"~a;
54     }
55     for(int i = 0; i < initial.length; i++)
56         if(initial[i] != newPath[i])
57             return false;
58     return true;
59 }
60 
61 ///Function is implemented AppDelegate.m
62 version(AppleOS)
63 private extern(C) const(char*) hipGetResourcesPath();
64 
65 abstract class HipFile : IHipFileItf
66 {
67     immutable FileMode mode;
68     immutable string path;
69     ulong size;
70     ulong cursor;
71     @disable this();
72     this(string path, FileMode mode)
73     {
74         this.mode = mode;
75         this.path = path;
76         open(path, mode);
77         this.size = getSize();
78     }
79     ///Whence is the same from libc
80     long seek(long count, int whence = SEEK_CUR)
81     {
82         switch(whence)
83         {
84             default:
85             case SEEK_CUR:
86                 cursor+= count;
87                 break;
88             case SEEK_END:
89                 cursor = size + count;
90                 break;
91             case SEEK_SET:
92                 cursor = count;
93                 break;
94         }
95         return cast(long)cursor;
96     }
97 
98     T[] rawRead(T)(T[] buffer)
99     {
100         read(cast(void*)buffer.ptr,buffer.length);
101         return buffer;
102     }
103 }
104 
105 
106 private class HipFSPromise : IHipFSPromise
107 {
108     string filename;
109     bool finished = false;
110     ubyte[] data;
111     void delegate(in ubyte[] data)[] onSuccessList;
112     void delegate(string err)[] onErrorList;
113     this(string filename){this.filename = filename;}
114     IHipFSPromise addOnSuccess(void delegate(in ubyte[] data) onSuccess)
115     {
116         if(finished)
117             onSuccess(data);
118         else
119             onSuccessList~=onSuccess;
120         return this;
121     }
122     IHipFSPromise addOnError(void delegate(string error) onError)
123     {
124         if(finished && !data.length)
125             onError("No data");
126         else
127             onErrorList~= onError;
128         return this;
129     }
130     void setFinished(ubyte[] data)
131     {
132         import std.stdio;
133         this.data = data;
134         this.finished = true;
135         if(data) foreach(success; onSuccessList)
136             success(data);
137         else foreach(err; onErrorList)
138             err("Could not read file");
139 
140     }
141     bool resolved() const{return finished;}
142 }
143 
144 /**
145 * FileSystem access for specific platforms.
146 */
147 class HipFileSystem
148 {
149     protected __gshared string defPath;
150     protected __gshared string initialPath = "";
151     protected __gshared string combinedPath;
152     protected __gshared bool isInstalled;
153     protected __gshared IHipFileSystemInteraction fs;
154     protected __gshared size_t filesReadingCount = 0;
155 
156     protected __gshared bool function(string path, out string errMessage)[] extraValidations;
157 
158     version(Android){import hip.filesystem.systems.android;}
159     else version(UWP){import hip.filesystem.systems.uwp;}
160     else version(WebAssembly){import hip.filesystem.systems.browser;}
161     else version(PSVita){import hip.filesystem.systems.cstd;}
162     else version(CustomRuntimeTest){import hip.filesystem.systems.cstd;}
163     else version(HipDStdFile){import hip.filesystem.systems.dstd;}
164     else {import hip.filesystem.systems.cstd;}
165 
166     public static void initializeAbsolute()
167     {
168         if(fs is null)
169         {
170             version(Android){fs = new HipAndroidFileSystemInteraction();}
171             else version(UWP){fs = new HipUWPileSystemInteraction();}
172             else version(PSVita){fs = new HipCStdioFileSystemInteraction();}
173             else version(CustomRuntimeTest){fs = new HipCStdioFileSystemInteraction();}
174             else version(WebAssembly){fs = new HipBrowserFileSystemInteraction();}
175             else
176             {
177                 version(HipDStdFile){}else{static assert(false, "HipDStdFile should be marked to be used.");}
178                 fs = new HipStdFileSystemInteraction();
179             }
180         }
181     }
182  
183     
184     public static void install(string path)
185     {
186         import hip.util.system : sanitizePath;
187         if(!isInstalled)
188         {
189             initialPath = path.sanitizePath;
190             setPath("");
191             isInstalled = true;
192         }
193     }
194     /**
195     *   This function may be refactored in future since having different
196     *   directories to resources to writeable paths is becoming more common
197     */
198     version(AppleOS)
199     public static string getResourcesPath()
200     {
201         import core.stdc.string;
202         auto str = hipGetResourcesPath;
203         return cast(string)str[0..strlen(str)];
204     }
205 
206     
207     public static void install(string path,
208     bool function(string path, out string errMessage)[] validations ...)
209     {
210         import hip.util.system : sanitizePath;
211         if(!isInstalled)
212         {
213             install(path);
214             foreach (v; validations){extraValidations~=v;}
215         }
216     }
217     @ExportD public static string getPath(string path)
218     {
219         import hip.util.path:joinPath;
220         import hip.util.system : sanitizePath;
221         import hip.console.log;
222 
223         if(combinedPath)
224             return joinPath(combinedPath, path.sanitizePath);
225         return path.sanitizePath;
226     }
227     @ExportD public static bool isPathValidExtra(string path)
228     {
229         import hip.error.handler;
230         import hip.util.system : sanitizePath;
231         path = path.sanitizePath;
232         string err;
233         foreach (bool function(string, out string) validation; extraValidations)
234         {
235             if(!validation(path, err))
236             {
237                 ErrorHandler.showErrorMessage("HipFileSystem validation error",
238                 "Path '"~path~"' failed at validation with error: '"~err~"'.");
239                 return false;
240             }
241         }
242         return true;
243     }
244     
245     @ExportD public static bool isPathValid(string path, bool expectsFile = true, bool shouldVerify = true)
246     {
247         import hip.error.handler;
248         if(!initialPath) return false;
249         if(!validatePath(initialPath, defPath~path))
250         {
251             ErrorHandler.showErrorMessage("Path failed default validation: can't reference external path.", path);
252             return false;
253         }
254         if(shouldVerify)
255         {
256             if((expectsFile && !HipFS.absoluteIsFile(path)) || (!expectsFile && !HipFS.absoluteIsDir(path)))
257             {
258                 ErrorHandler.showErrorMessage("Path failed default validation: Expected '"~ (expectsFile ? "file" : "directory") ~ 
259                 "' but received "~ (expectsFile ? "'directory'" : "'file'"), path);
260                 return false;
261             }
262         }
263 
264         return isPathValidExtra(path);
265     }
266 
267     @ExportD public static bool setPath(string path)
268     {
269         import hip.util.path:joinPath;
270         import hip.util.system : sanitizePath;
271         import hip.console.log;
272         if(path)
273         {
274             defPath = path.sanitizePath;
275             combinedPath = joinPath(initialPath, defPath);
276         }
277         else
278             combinedPath = initialPath;
279         return validatePath(initialPath, combinedPath);
280     }
281 
282     private static void defaultErrorHandler(string err = "")
283     {
284         import hip.error.handler;
285         filesReadingCount--;
286         ErrorHandler.assertExit(false, "HipFS Error: "~err);
287     }
288     
289     @ExportD public static IHipFSPromise read(string path)
290     {
291         import hip.console.log;
292         hiplog("Required path ", path);
293         path = getPath(path);
294         if(!isPathValid(path))
295             return null;
296         hiplog("Path validated.");
297         filesReadingCount++;
298 
299         HipFSPromise promise = new HipFSPromise(path);
300         fs.read(path, (ubyte[] data)
301         {
302             filesReadingCount--;
303             promise.setFinished(data);
304         }, (string err)
305         {
306             promise.setFinished(null);
307             defaultErrorHandler(err);
308         });
309         
310         return promise;
311     }
312 
313     @ExportD public static IHipFSPromise readText(string path)
314     {
315         IHipFSPromise ret = read(path);
316         // if(ret)
317         // {
318         //     import std.utf;
319         //     output = toUTF8((cast(string)data));
320         // }
321         return ret;
322     }
323     
324 
325     version(HipDStdFile)
326     {
327         import std.stdio:File;
328         public static bool getFile(string path, string opts, out File file)
329         {
330             if(!isPathValid(path))
331                 return false;
332             file = File(getPath(path), opts);
333             return true;
334         }
335 
336     } 
337 
338     @ExportD public static bool write(string path, void[] data)
339     {
340         if(!isPathValid(path))
341             return false;
342         return fs.write(getPath(path), data);
343     }
344     @ExportD public static bool exists(string path){return isPathValid(path) && fs.exists(getPath(path));}
345     @ExportD public static bool remove(string path)
346     {
347         if(!isPathValid(path))
348             return false;
349         return fs.remove(getPath(path));
350     }
351 
352     @ExportD public static string getcwd()
353     {
354         return getPath("");
355     }
356 
357     @ExportD public static bool absoluteExists(string path){return fs.exists(path);}
358     @ExportD public static bool absoluteIsDir(string path){return fs.isDir(path);}
359     @ExportD public static bool absoluteIsFile(string path){return fs.isFile(path);}
360     @ExportD public static bool absoluteRemove(string path){return fs.remove(path);}
361     @ExportD public static bool absoluteRead(string path, out void[] output)
362     {
363         ///This may need to be refactored in the future.
364         // import std.functional:toDelegate;
365         return fs.read(path, (void[] data){output = data;}, (err) => defaultErrorHandler(err));
366     }
367     @ExportD("ubyte") public static bool absoluteRead(string path, out ubyte[] output)
368     {
369         void[] data;
370         bool ret = absoluteRead(path, data);
371         output = cast(ubyte[])data;
372         return ret;
373     }
374 
375     @ExportD public static bool absoluteReadText(string path, out string output)
376     {
377         void[] data;
378         bool ret = absoluteRead(path, data);
379         if(ret)
380             output = cast(string)data;
381         return ret;
382     }
383 
384 
385     @ExportD public static bool isDir(string path){return isPathValid(path, false, false) && fs.isDir(getPath(path));}
386     @ExportD public static bool isFile(string path){return isPathValid(path, true, false) && fs.isFile(getPath(path));}
387 
388     @ExportD public static string writeCache(string cacheName, void[] data)
389     {
390         import hip.util.path:joinPath;
391         string p = joinPath(initialPath, ".cache", cacheName);
392         write(p, data);
393         return p;
394     }
395 }
396 
397 alias HipFS = HipFileSystem;